module Rinf

class Inf
	include Enumerable
	attr_accessor :sections
	def initialize(data)
		parse(data)
	end
	def parse(data)
		@sections = {}
		@order = []
		tmp = data.clone
		lines = tmp.split("\n")
		while lines.length > 0
			section = Section.new(lines, self)
			break unless section.name
			@sections[section.name] = section
			@order << section
		end
		resolve_references
	end
	def resolve_references
		@sections.each do |name, s|
			s.atoms.each do |a|
				a.resolve_references
			end
		end
	end
	def [](key)
		if key.kind_of? Numeric
			@order[key]
		else
			@sections[key]
		end
	end
	def []=(key, new_val)
		if key.kind_of? Numeric
			@order[key] = new_val
		else
			@sections[key] = new_val
		end
	end
	def each
		@sections.each {|k,v| yield(k,v) }
	end
	def each_order
		@order.each {|s| yield(s) }
	end
	def length
		@sections.length
	end
	def inspect
		"#<Inf length=#{length}>"
	end
	def to_s(opts={})
		@order.map {|section| section.to_s(opts) }.join("\n\n")
	end
end

class Commentable
	attr_accessor :comment
	def to_s_comment
		@comment ? " ; #{@comment}" : ""
	end
end

class Section < Commentable
	include Enumerable

	attr_accessor :name, :entries, :values, :atoms, :parent
	def initialize(lines, parent)
		@parent = parent
		@atoms = []
		parse(lines)
	end
	def parse(lines)
		@entries = []
		@values = {}
		cleanup_data!(lines)
		@name = find_name!(lines)
		while lines.length > 0
			# start of a new section, not for us
			break if match_section_header?(lines[0])
			line = lines.shift
			next if line.lstrip.rstrip == ""
			entry = Item.parse(line, self)
			next if entry.empty?
			@entries << entry if entry
		end
	end
	def find_name!(lines)
		res = nil
		while lines.length > 0
			line = lines.shift
			break if (res=match_section_header?(line))
		end
		res
	end
	def match_section_header?(line)
		line =~ /^\[([[:alpha:][:digit:]_\-.]+)\]$/
		$1
	end
	def cleanup_data!(lines)
		resolve_multilines!(lines)
	end
	def resolve_multilines!(lines)
		idx = 0
		while idx < lines.length
			line = lines[idx]
			start_idx = idx
			idx += 1
			next unless line =~ /^(.*)\\$/
			res = $1.lstrip.rstrip
			line = lines[idx]
			while line && line =~ /^(.*)\\$/
				res << $1.lstrip.rstrip
				idx += 1
				line = lines[idx]
			end
			res << line.lstrip.rstrip
			lines[start_idx].replace(res)
			(idx - start_idx).times do |i|
				lines.delete_at(start_idx + 1)
			end
		end
	end
	def [](key)
		if key.kind_of? Numeric
			@entries[key]
		else
			@values[key]
		end
	end
	def []=(key, val)
		if key.kind_of? Numeric
			@entries[key] = val
		else
			@values[key] = val
		end
	end
	def length
		@entries.length
	end
	def inspect
		"#<Section(#{@name}):#{length}>"
	end
	def to_s(opts={})
		res = "[#{@name}]#{to_s_comment}"
		res << "\n" + @entries.map {|e| "    #{e.to_s(opts)}"}.join("\n") if @entries.length > 0
		res
	end
	def each
		@entries.each {|e| yield e }
	end
	def each_key_val
		@values.each {|k,v| yield(k,v) }
	end
end

class Item < Commentable
	def self.parse(line, parent, not_klass=nil)
		# KeyValueItems take precedence over ListItems
    	# eg, CopyAlways.En-us.Files=11,"en-us" becomes a KeyValueItem with a list
		# as its value
		if not_klass != KeyValueItem && KeyValueItem.match?(line)
			KeyValueItem.new(line, parent)
		elsif not_klass != ListItem && ListItem.match?(line)
			ListItem.new(line, parent)
		else
			Atom.new(line, parent)
		end
	end
	attr_accessor :parent
	def initialize(line, section)
		@parent = section
		parse!(line)
	end
	def parse!(line)
		raise "Item::parse! is not meant to be used directly - use KeyValueItem or ListItem instead"
	end
	def to_s(opts={})
		raise "Item::to_s is not meant to be used directly - use KeyValueItem or ListItem instead"
	end
	def resolve!
		raise "Item::resolve! is not meant to be used directly - use KeyValueItem or ListItem instead"
	end
end

class KeyValueItem < Item
	def self.match?(line)
		!!(line =~ /^[^;]+=/)
	end
	attr_accessor :key, :value
	def parse!(line)
		unless line =~ /^([^=]+)?=([^;]*)/
			raise "Don't know how that happened - you must be doing something weird"
		end
		@key = Atom.new($1.lstrip.rstrip, self)
		@value = Item.parse($2 || "", self, KeyValueItem)
		tmp = $~.post_match
		if tmp =~ /;(.*)/
			@comment = $1.lstrip.rstrip
		end
		@parent[@key.value] = @value
	end
	def inspect
		"#<KeyValueItem key=#{@key.inspect} value=#{@value.inspect}>"
	end
	def to_s(opts={})
		"#{@key.to_s(opts)}=#{@value.to_s(opts)}#{to_s_comment}"
	end
	def resolve!
		@key.resolve!
		@value.resolve!
	end
	def empty?
		return false
	end
end

class ListItem < Item
	include Enumerable

	def self.match?(line)
		tmp = line.clone
		in_quote = false
		prev_char = nil
		quote_char = nil
		while tmp.length > 0
			c = tmp.slice!(0,1)
			if (c == '"' || c == "'") && !in_quote && prev_char != "/"
				in_quote = true
				quote_char = c
			elsif c == quote_char && prev_char != "/" && in_quote
				in_quote = false
			elsif c == ";" && !in_quote
				return false
			end
			if c == "," && in_quote == false
				return true
			end
			prev_char = c
		end
		false
	end
	attr_accessor :items
	def empty?
		return false
	end
	def parse!(line)
		@items = []
		tmp = line.lstrip.rstrip.clone
		curr_item = ""
		prev_char = ""
		in_quote = false
		quote_char = nil
		was_quoted = false
		while tmp.length > 0
			c = tmp.slice!(0,1)
			if c =~ /["']/ && prev_char != "\\" && !in_quote
				quote_char = c
				in_quote = true
				was_quoted = true
			elsif c == quote_char && in_quote && prev_char != "\\"
				in_quote = !in_quote
				quote_char = nil
			elsif !in_quote && c == ";"
				com = tmp.slice(0,tmp.length).lstrip.rstrip
				while com[0,1] == ";" do
					com.slice!(0,1)
					com = com.lstrip.rstrip
				end
				@parent.comment = @comment = com
				break
			elsif in_quote && prev_char == "\\" && c == quote_char
				curr_item.slice!(-1,1)
			end
			prev_char = c
			if c == "," && !in_quote
				@items << curr_item
				curr_item = ""
				next
			end
			curr_item << c
		end
		@items << curr_item
		@items = @items.map {|i| Atom.new(i.lstrip.rstrip, self) }
	end
	def length
		@items.length
	end
	def [](idx)
		@items[idx]
	end
	def []=(idx,val)
		val = Item.parse(val, self) unless val.kind_of? Atom
		@items[idx] = val
	end
	def each
		@items.each {|i| yield i }
	end
	def inspect
		@items.inspect
	end
	def to_s(opts={})
		@items.map do |i|
			i.to_s(opts)
		end.join(",")
	end
	def resolve!(inf)
	end
end

class Atom < Commentable
	class << self
		attr_accessor :creations
	end

	attr_accessor :value, :quoted, :ref_values, :section_ref
	def initialize(val, parent)
		val = val.lstrip.rstrip
		if (val[0,1] == "'" && val[-1,1] == "'") || (val[0,1] == '"' && val[-1,1] == '"')
			val = val.slice(1, val.length-2)
			@quoted = true
		end
		@parent = parent
		@comment = nil
		@value = ""
		if val =~ /^;[[:space:]]*(.*$)/
			@comment = $1
		else
			@value = val
		end
		@ref_values = []
		@section_ref = nil
		section.atoms << self
	end
	def empty?
		return @value.lstrip.rstrip == ""
	end
	def section
		walk_parents_till(Section)
	end
	def inf
		walk_parents_till(Inf)
	end
	def walk_parents_till(klass)
		res = @parent
		until res.kind_of?(klass)
			res = res.parent
		end
		res
	end
	def inspect
		@value.inspect
	end
	def value=(val)
		@quoted = true if val =~ /,/
		@value = val
	end
	def resolve_references
		return unless @value
		@ref_values = resolve_variable_refs
		@section_ref = resolve_section_ref
	end
	def resolve_section_ref
		@section_ref = SectionReference.new(@value) if inf[@value]
	end
	def refd_section
		if @section_ref
			@section_ref.resolve(inf)
		else
			nil
		end
	end
	def resolve_variable_refs
		counter = 0
		res = []
		tmp = @value.clone
		if VariableReference.match?(tmp)
			regex = /(%[^%]+%)/
			md = regex.match(tmp)
			before = after = nil
			while md
				sidx, eidx = md.offset(0)
				before = tmp[0, sidx] if sidx != 0
				ref = VariableReference.new($1)
				after = tmp[eidx, tmp.length]
				res << before
				res << ref
				tmp = after
				md = regex.match(tmp)
			end
		end
		res << tmp
		res
	end
	def resolve!
		@value = to_s('resolve'=>true)
	end
	def var_resolve(opts={})
		to_s({'resolve'=>true}.merge(opts))
	end
	def resolve
		return @section_ref.resolve(inf) if @section_ref
		to_s('resolve'=>true)
	end
	# can only resolve types of VariableReference
	def to_s(opts={})
		defaults = {'quotes'=>true}
		opts = defaults.merge(opts)
		if opts['resolve']
			res = @ref_values.map do |rv|
				# sections references don't make a diff when to_s'd
				if rv.kind_of?(String) || rv.kind_of?(SectionReference)
					rv
				elsif rv.kind_of?(VariableReference)
					tmp_res = rv.resolve(inf)
					if tmp_res
						tmp_res.to_s('quotes'=>false) # (!@quoted))# && tmp_res.to_s =~ / /))
					else
						""
					end
				end
			end.join
			res = '"' + res + '"' if @quoted && opts['quotes']
			res
		else
			quotes = (@quoted && opts['quotes']) ? '"' : ""
			res = "#{quotes}#{@value}#{quotes}"
		end
		res + to_s_comment
	end
end

class Reference
	attr_accessor :referenced_value
	def initialize(value)
		@referenced_value = value.clone
	end
	def resolve(inf)
		raise "This should be overridden by inheriting classes"
	end
	def inspect_name
		"R"
	end
	def inspect
		"#<#{inspect_name}(#{@referenced_value})>"
	end
end

class SectionReference < Reference
	def resolve(inf)
		inf[@referenced_value]
	end
	def inspect_name
		"SR"
	end
end

class VariableReference < Reference
	def self.match?(val)
		!!(val =~ /%[^%]+%/)
	end
	attr_accessor :variable_name
	def initialize(value)
		super(value)
		@variable_name = $1 if @referenced_value =~ /%([^%]+)%/
	end
	def resolve(inf)
		begin
			res = inf['Strings'][@variable_name]
			res = $1 if res =~ /^"(.*)"$/ || res =~ /^'(.*)'$/
			res
		rescue Exception=>ex
			nil
		end
	end
	def inspect_name
		"VR"
	end
end

end
